package com.shiku.imserver.hander;

import com.shiku.imserver.common.http.HttpRequest;
import com.shiku.imserver.common.http.HttpRequestDecoder;
import com.shiku.imserver.common.http.HttpResponse;
import com.shiku.imserver.common.http.HttpResponseEncoder;
import com.shiku.imserver.common.message.PacketVO;
import com.shiku.imserver.common.packets.ImPacket;
import com.shiku.imserver.common.ws.IWsMsgHandler;
import com.shiku.imserver.common.ws.WsPacket;
import com.shiku.imserver.common.ws.WsRequest;
import com.shiku.imserver.common.ws.WsResponse;
import com.shiku.imserver.common.ws.WsServerConfig;
import com.shiku.imserver.common.ws.WsServerDecoder;
import com.shiku.imserver.common.ws.WsServerEncoder;
import com.shiku.imserver.common.ws.WsSessionContext;
import com.shiku.imserver.service.IMBeanUtils;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.GroupContext;
import org.tio.core.Tio;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.http.common.HeaderName;
import org.tio.http.common.HeaderValue;
import org.tio.http.common.HttpResponseStatus;
import org.tio.utils.hutool.StrUtil;
import org.tio.websocket.common.Opcode;
import org.tio.websocket.common.util.BASE64Util;
import org.tio.websocket.common.util.SHA1Util;

public class WsServerAioHandler extends AbstractProtocolHandler {
    private static Logger log = LoggerFactory.getLogger(WsServerAioHandler.class);
    private IWsMsgHandler wsMsgHandler;
    private WsServerConfig wsServerConfig;

    public WsServerAioHandler(WsServerConfig wsServerConfig2, IWsMsgHandler wsMsgHandler2) {
        this.wsServerConfig = wsServerConfig2;
        this.wsMsgHandler = wsMsgHandler2;
    }

    public WsRequest decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
        WsSessionContext wsSessionContext = (WsSessionContext) channelContext.getAttribute();
        if (!wsSessionContext.isHandshaked()) {
            HttpRequest request = HttpRequestDecoder.decode(buffer, limit, position, readableLength, channelContext, this.wsServerConfig);
            if (request == null) {
                return null;
            }
            HttpResponse httpResponse = updateWebSocketProtocol(request, channelContext);
            if (httpResponse == null) {
                throw new AioDecodeException("http协议升级到websocket协议失败");
            }
            httpResponse.setCommand( (short)2);
            wsSessionContext.setHandshakeRequest(request);
            wsSessionContext.setHandshakeResponse(httpResponse);
            wsSessionContext.setHandshaked(true);
            Tio.send(channelContext, httpResponse);
            WsRequest wsRequestPacket = new WsRequest();
            wsRequestPacket.setHandShake(true);
            wsRequestPacket.setCommand( (short)1);
            return wsRequestPacket;
        }
        WsRequest websocketPacket = null;
		try {
			websocketPacket = WsServerDecoder.decode(buffer, channelContext);
		} catch (UnsupportedEncodingException | AioDecodeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        if (websocketPacket != null) {
            websocketPacket.setWSBytes(websocketPacket.getBytes());
            if (websocketPacket.getWsOpcode() == Opcode.CLOSE) {
                websocketPacket.setCommand( (short)7);
            }
        }
        return websocketPacket;
    }

    public Packet decode(ByteBuffer buffer, ChannelContext channelContext) throws AioDecodeException {
        return null;
    }

    public ByteBuffer encode(Packet packet, GroupContext groupContext, ChannelContext channelContext) {
        ImPacket imPacket = (ImPacket) packet;
        if (imPacket.getCommand() != 2) {
            return WsServerEncoder.encode(new WsPacket(imPacket.getCommand(), imPacket.getBytes()), groupContext, channelContext);
        }
        try {
            return HttpResponseEncoder.encode(((WsSessionContext) channelContext.getAttribute()).getHandshakeResponse(), groupContext, channelContext);
        } catch (UnsupportedEncodingException e) {
            log.error(e.toString(), e);
            return null;
        }
    }

    public WsServerConfig getHttpConfig() {
        return this.wsServerConfig;
    }

    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
        PacketVO result = IMBeanUtils.getMessageProcess().dispatch((ImPacket) packet, channelContext);
        if (result != null) {
            Tio.bSend(channelContext, new WsPacket(result.getCmd(), result.getBytes()));
        }
    }

    private WsResponse h(WsRequest websocketPacket, byte[] bytes, Opcode opcode, ChannelContext channelContext) throws Exception {
        if (opcode == Opcode.TEXT) {
            if (bytes == null || bytes.length == 0) {
                Tio.remove(channelContext, "错误的websocket包，body为空");
                return null;
            }
            return processRetObj(this.wsMsgHandler.onText(websocketPacket, new String(bytes, this.wsServerConfig.getCharset()), channelContext), "onText", channelContext);
        } else if (opcode == Opcode.BINARY) {
            if (bytes != null && bytes.length != 0) {
                return processRetObj(this.wsMsgHandler.onBytes(websocketPacket, bytes, channelContext), "onBytes", channelContext);
            }
            Tio.remove(channelContext, "错误的websocket包，body为空");
            return null;
        } else if (opcode == Opcode.PING || opcode == Opcode.PONG) {
            log.debug("收到" + opcode);
            return null;
        } else if (opcode == Opcode.CLOSE) {
            return processRetObj(this.wsMsgHandler.onClose(websocketPacket, bytes, channelContext), "onClose", channelContext);
        } else {
            Tio.remove(channelContext, "错误的websocket包，错误的Opcode");
            return null;
        }
    }

    private WsResponse processRetObj(Object obj, String methodName, ChannelContext channelContext) throws Exception {
        if (obj == null) {
            return null;
        }
        if (obj instanceof String) {
            return WsResponse.fromText((String) obj, this.wsServerConfig.getCharset());
        }
        if (obj instanceof byte[]) {
            return WsResponse.fromBytes((byte[]) obj);
        }
        if (obj instanceof WsResponse) {
            return (WsResponse) obj;
        }
        if (obj instanceof ByteBuffer) {
            return WsResponse.fromBytes(((ByteBuffer) obj).array());
        }
        log.error("{} {}.{}()方法，只允许返回byte[]、ByteBuffer、WsResponse或null，但是程序返回了{}", new Object[]{channelContext, getClass().getName(), methodName, obj.getClass().getName()});
        return null;
    }

    public void setHttpConfig(WsServerConfig httpConfig) {
        this.wsServerConfig = httpConfig;
    }

    public static HttpResponse updateWebSocketProtocol(HttpRequest request, ChannelContext channelContext) {
        String Sec_WebSocket_Key = request.getHeaders().get("sec-websocket-key");
        if (!StrUtil.isNotBlank(Sec_WebSocket_Key)) {
            return null;
        }
        String acceptKey = BASE64Util.byteArrayToBase64(SHA1Util.SHA1(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
        HttpResponse httpResponse = new HttpResponse(request);
        httpResponse.setStatus(HttpResponseStatus.C101);
        Map<HeaderName, HeaderValue> respHeaders = new HashMap<>();
        respHeaders.put(HeaderName.Connection, HeaderValue.Connection.Upgrade);
        respHeaders.put(HeaderName.Upgrade, HeaderValue.Upgrade.WebSocket);
        respHeaders.put(HeaderName.Sec_WebSocket_Accept, HeaderValue.from(acceptKey));
        httpResponse.addHeaders(respHeaders);
        return httpResponse;
    }
}
